package chat

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"geekai/api/dto/response"
	"geekai/core"
	"geekai/core/types"
	logger2 "geekai/logger"
	"geekai/model"
	"geekai/service"
	"geekai/service/oss"
	"geekai/utils"
	req2 "github.com/imroc/req/v3"
	"github.com/sashabaranov/go-openai"
	"gorm.io/gorm"
	"html/template"
	"io"
	"runtime/debug"
	"strings"
	"time"
	"unicode/utf8"
)

var log = logger2.GetLogger()

type OpenAiChatService struct {
	app           *core.AppServer
	db            *gorm.DB
	uploadManager *oss.UploaderManager
	ReqCancelFunc *types.LMap[string, context.CancelFunc]
	// 聊天上下文，KEY是会话Id，value是[]Message消息
	ChatContexts *types.LMap[string, []interface{}]
	userService  *service.UserService
}

func NewOpenAiChatService(app *core.AppServer, db *gorm.DB, manager *oss.UploaderManager, userService *service.UserService) *OpenAiChatService {
	return &OpenAiChatService{
		db:            db,
		app:           app,
		uploadManager: manager,
		userService:   userService,
		ReqCancelFunc: types.NewLMap[string, context.CancelFunc](),
		ChatContexts:  types.NewLMap[string, []interface{}](),
	}
}

func NewOpenAiClient(token string, baseUrl string) *openai.Client {
	config := openai.DefaultConfig(token)
	config.BaseURL = baseUrl + "/v1"
	client := openai.NewClientWithConfig(config)
	return client
}

// Chat 大模型对话完整逻辑调用
func (s *OpenAiChatService) Chat(ctx context.Context, session *types.ChatSession, role model.ChatRole, prompt string, ws *types.WsClient) error {
	// 不是 debug 模式下，捕获panic错误
	if !s.app.Debug {
		defer func() {
			if r := recover(); r != nil {
				log.Errorf("sendMessage 向大模型发送消息失败：%v", r)
				fmt.Println(string(debug.Stack()))
				utils.SendChunkMsg(ws, fmt.Sprintf("sendMessage 向大模型发送消息失败：%v", r))
			}
		}()
	}
	// 检查用户信息
	userVo, err := s.checkUserInfo(session)
	if err != nil {
		return err
	}
	// 检查提示词消耗Token
	promptTokens, err := s.checkPromptToken(prompt, session)
	if err != nil {
		return err
	}

	// 构建大模型请求参数
	openAIReq := openai.ChatCompletionRequest{
		Model:       session.Model.Value,
		MaxTokens:   session.Model.MaxTokens,
		Temperature: session.Model.Temperature,
		Stream:      session.Stream,
	}
	// 加载函数工具
	tools := s.loadTool(session)
	if tools != nil {
		openAIReq.Tools = tools
		openAIReq.ToolChoice = "auto"
		// 并行工具调用，一次可以调用多个工具
		openAIReq.ParallelToolCalls = true
	}

	// 加载聊天上下文
	chatCtxs := s.loadChatCtx(session, role, openAIReq, promptTokens)
	// 对提示词中特定的文件URL进行处理
	promptFileContent, err := s.handlerPromptFileUrl(prompt, session)
	if err != nil {
		return err
	}
	log.Debugf("提取文件后的提示词：%s\n", promptFileContent)

	// 处理提示词中图片URL
	chatMsg := s.handlerPromptImgUrl(prompt)
	if chatMsg != nil {
		chatCtxs = append(chatCtxs, *chatMsg)
	} else {
		chatCtxs = append(chatCtxs, openai.ChatCompletionMessage{
			Role:    openai.ChatMessageRoleUser,
			Content: promptFileContent,
		})
	}
	openAIReq.Messages = chatCtxs
	// 发送请求
	return s.sendOpenAiMessage(ctx, openAIReq, *userVo, session, role, prompt, ws)
}

// StopGenerate 停止大模型生成
func (s *OpenAiChatService) StopGenerate(clientId string) {
	if s.ReqCancelFunc.Has(clientId) {
		s.ReqCancelFunc.Get(clientId)()
		s.ReqCancelFunc.Delete(clientId)
	}
}

// sendOpenAiMessage 具体请求大模型逻辑组装
func (s *OpenAiChatService) sendOpenAiMessage(ctx context.Context, req openai.ChatCompletionRequest,
	userResp response.User, session *types.ChatSession, role model.ChatRole, prompt string, ws *types.WsClient) error {
	// 记录提问时间
	promptCreatedAt := time.Now()
	// 开始发送请求时间
	start := time.Now()
	handleErr := func(err error) error {
		if err == nil {
			return nil
		}
		switch {
		case strings.Contains(err.Error(), "context canceled"):
			return fmt.Errorf("用户取消了请求：%s", prompt)
		case strings.Contains(err.Error(), "no available key"):
			return errors.New("抱歉😔😔😔，系统已经没有可用的 API KEY，请联系管理员！")
		default:
			return err
		}
	}
	// 判断是不是流式请求
	if req.Stream {
		streamResp, err := s.chatCompletionRequestStream(ctx, req, session)
		log.Infof("HTTP请求完成，耗时：%d", time.Now().Sub(start))
		if err := handleErr(err); err != nil {
			if streamResp != nil {
				streamResp.Close()
			}
			return err
		}
		defer streamResp.Close()
		if err := s.handlerChatCompletionStreamResponse(req, streamResp, role, session, ws, userResp, prompt, promptCreatedAt); err != nil {
			return err
		}
	} else {
		// 发送请求
		simpleResp, err := s.chatCompletionRequest(ctx, req, session)
		log.Infof("HTTP请求完成，耗时：%d", time.Now().Sub(start))
		if err := handleErr(err); err != nil {
			return err
		}
		// 处理响应数据
		if err := s.handlerChatCompletionSyncResponse(req, simpleResp, role, session, ws, userResp, prompt, promptCreatedAt); err != nil {
			return err
		}
	}
	return nil
}

// handlerChatCompletionStreamResponse 处理流式请求响应数据
func (s *OpenAiChatService) handlerChatCompletionStreamResponse(req openai.ChatCompletionRequest,
	streamResp *openai.ChatCompletionStream, role model.ChatRole, session *types.ChatSession,
	ws *types.WsClient, userResp response.User, prompt string, promptCreatedAt time.Time) error {
	// 记录回复时间
	replyCreatedAt := time.Now()
	// 存储我们读取出来的内容
	var contents = make([]string, 0)
	// 存储要执行的工具
	toolCallsMap := make(map[int]*types.ToolCall)
	// 循环读取消息
	for {
		respData, err := streamResp.Recv()
		bytes, _ := json.Marshal(respData)
		log.Infof("流式对话响应：%s", string(bytes))
		if errors.Is(err, io.EOF) {
			log.Infoln("Stream finished")
			break
		}
		if err != nil {
			if strings.Contains(err.Error(), "context canceled") {
				log.Infof("用户取消了请求：%s\n", err.Error())
				break
			}
			return fmt.Errorf("流式读取大模型响应数据出现错误：%v", err)
		}

		if len(respData.Choices) == 0 {
			continue
		}
		// 判断 Content 和 ToolCalls 都为空 没有返回数据
		if respData.Choices[0].Delta.Content == "" && respData.Choices[0].Delta.ToolCalls == nil {
			continue
		}
		// 判断 FinishReason 是否等于 stop，如果等于就表示大模型停止输出，判断 contents 是否等于0，如果等于就表示大模型第一次响应就停止了
		if respData.Choices[0].FinishReason == openai.FinishReasonStop && len(contents) == 0 {
			utils.SendChunkMsg(ws, "抱歉😔😔😔，AI助手由于未知原因已经停止输出内容。")
			break
		}

		// 处理工具调用
		s.handlerToolResponse(respData.Choices[0].Delta.ToolCalls, toolCallsMap)

		/*// 如果结束原因为工具调用，则处理
		if respData.Choices[0].FinishReason == openai.FinishReasonToolCalls {
			// 注意：在流式响应中，结束原因会在最后一个片段中设置
			// 但是，这里我们可能已经收集了所有的工具调用片段，但finish_reason会在最后一个delta设置时出现
			// 所以，这里我们退出流读取循环，然后执行工具
			break
		}*/

		// 输出完成或者输出中断了
		if respData.Choices[0].FinishReason != "" {
			break
		} else {
			// 获取大模型回复内容
			content := respData.Choices[0].Delta.Content
			if content != "" {
				// 把内存存储起来
				contents = append(contents, content)
				// 响应给前端
				utils.SendChunkMsg(ws, content)
			}
		}
	}
	// 执行工具
	err := s.executeTools(toolCallsMap, ws, &contents, userResp)
	if err != nil {
		return err
	}
	// 保存大模型聊天消息
	if len(contents) > 0 {
		usage := response.UsageResp{
			Prompt:           prompt,
			Content:          strings.Join(contents, ""),
			PromptTokens:     0,
			CompletionTokens: 0,
			TotalTokens:      0,
		}
		var message = types.Message{Role: "assistant", Content: usage.Content}
		// 保存会话聊天记录
		s.saveChatHistory(req, usage, message, session, role, userResp, promptCreatedAt, replyCreatedAt)
	}
	return nil
}

// handlerChatCompletionSyncResponse 处理同步请求响应数据
func (s *OpenAiChatService) handlerChatCompletionSyncResponse(req openai.ChatCompletionRequest,
	resp openai.ChatCompletionResponse, role model.ChatRole, session *types.ChatSession,
	ws *types.WsClient, userResp response.User, prompt string, promptCreatedAt time.Time) error {
	var openAiResp response.OpenAIResp
	// 存储要执行的工具
	toolCallsMap := make(map[int]*types.ToolCall)
	// 处理工具调用
	s.handlerToolResponse(resp.Choices[0].Message.ToolCalls, toolCallsMap)
	// 这里对响应对象进行编码在解码的原因是我们直接使用 utils.CopyObject() 函数进行对象拷贝时会出现报错
	respBytes, err := json.Marshal(resp)
	if err != nil {
		return fmt.Errorf("大模型响应数据编码失败：%v，body：%v", err, resp)
	}
	err = json.Unmarshal(respBytes, &openAiResp)
	if err != nil {
		return fmt.Errorf("大模型响应数据解码失败：%v，body：%v", err, resp)
	}
	content := openAiResp.Choices[0].Message.Content
	if content != "" {
		// 往前端响应消息
		utils.SendChunkMsg(ws, content)
	}
	// 执行工具
	contents := []string{content}
	err = s.executeTools(toolCallsMap, ws, &contents, userResp)
	if err != nil {
		return err
	}
	openAiResp.Usage.Prompt = prompt
	openAiResp.Usage.Content = strings.Join(contents, "")
	// 保存会话聊天记录
	s.saveChatHistory(req, openAiResp.Usage, openAiResp.Choices[0].Message, session, role, userResp, promptCreatedAt, time.Now())
	return nil
}

// handlerToolResponse 处理大模型响应的工具
func (s *OpenAiChatService) handlerToolResponse(toolCalls []openai.ToolCall, toolCallsMap map[int]*types.ToolCall) {
	var i int = 1
	for _, toolCall := range toolCalls {
		// toolCall 包含：Index（当前是第几个工具调用，从0开始），ID（可能为空或部分），Function（函数名或参数的一部分），只有流式输出才有 index，非流失输出没有 index
		if toolCall.Index != nil {
			idx := *toolCall.Index
			// 检查这个索引是否已经在map中
			if _, exists := toolCallsMap[idx]; !exists {
				toolCallsMap[idx] = &types.ToolCall{
					Type: string(toolCall.Type),
				}
			}
			tool := toolCallsMap[idx]
			// 处理函数名称
			if toolCall.Function.Name != "" {
				tool.Function.Name = toolCall.Function.Name
			}
			// 处理函数参数
			if toolCall.Function.Arguments != "" {
				tool.Function.Arguments += toolCall.Function.Arguments
			}
		} else {
			tool := &types.ToolCall{
				Type: string(toolCall.Type),
			}
			toolCallsMap[i] = tool
			// 处理函数名称
			if toolCall.Function.Name != "" {
				tool.Function.Name = toolCall.Function.Name
			}
			// 处理函数参数
			if toolCall.Function.Arguments != "" {
				tool.Function.Arguments += toolCall.Function.Arguments
			}
			i++
		}
	}
}

// saveChatHistory 保存消息
func (s *OpenAiChatService) saveChatHistory(req openai.ChatCompletionRequest, usage response.UsageResp,
	message types.Message, session *types.ChatSession,
	role model.ChatRole, userResp response.User,
	promptCreatedAt time.Time, replyCreatedAt time.Time) {
	// 更新上下文信息
	if s.app.SysConfig.EnableContext {
		chatCtx := req.Messages
		// 把大模型响应的消息放入到 chatCtx 中
		chatCtx = append(chatCtx, openai.ChatCompletionMessage{
			Role:    message.Role,
			Content: message.Content,
		})
		for _, v := range chatCtx {
			var chatDataCtx interface{} = v
			s.ChatContexts.Put(session.ChatId, append(s.ChatContexts.Get(session.ChatId), chatDataCtx))
		}
	}
	// promptTokens 用户提示词消耗token
	// replyTokens 大模型响应消耗token
	// totalTokens 总消耗的token
	var promptTokens, replyTokens, totalTokens int
	// 计算用户提示词消耗token
	if usage.PromptTokens > 0 {
		promptTokens = usage.PromptTokens
	} else {
		promptTokens, _ = utils.CalcTokens(usage.Prompt, req.Model)
	}

	// 用户提问消息
	historyUserMsg := model.ChatMessage{
		UserId:      userResp.Id,
		ChatId:      session.ChatId,
		RoleId:      role.Id,
		Type:        types.PromptMsg,
		Icon:        userResp.Avatar,
		Content:     template.HTMLEscapeString(usage.Prompt), // 对字符串进行 HTML 转义
		Tokens:      promptTokens,
		TotalTokens: promptTokens,
		UseContext:  true,
		Model:       req.Model,
	}
	historyUserMsg.CreatedAt = promptCreatedAt
	historyUserMsg.UpdatedAt = promptCreatedAt
	// 保存用户提问消息
	err := s.db.Save(&historyUserMsg).Error
	if err != nil {
		log.Errorf("保存用户提示词消息失败：%v\n", err)
	}
	// 计算本次对话消耗的总 token 数量
	if usage.CompletionTokens > 0 {
		replyTokens = usage.CompletionTokens
		totalTokens = usage.TotalTokens
	} else {
		// 计算模型输出内容消耗的token
		replyTokens, _ = utils.CalcTokens(usage.Content, req.Model)
		// replyTokens（模型响应token） + getTotalTokens(req)（用户请求输入的提示词token）
		totalTokens = replyTokens + getTotalTokens(req)
	}

	// 大模型响应消息
	historyReplyMsg := model.ChatMessage{
		UserId:      userResp.Id,
		ChatId:      session.ChatId,
		RoleId:      role.Id,
		Type:        types.ReplyMsg,
		Icon:        role.Icon,
		Content:     usage.Content,
		Tokens:      replyTokens,
		TotalTokens: totalTokens,
		UseContext:  true,
		Model:       req.Model,
	}
	historyReplyMsg.CreatedAt = replyCreatedAt
	historyReplyMsg.UpdatedAt = replyCreatedAt
	// 保存模型响应消息
	err = s.db.Save(&historyReplyMsg).Error
	if err != nil {
		log.Errorf("保存大模型响应消息失败：%v", err)
	}
	// 更新用户算力
	if session.Model.Power > 0 {
		s.subUserPower(userResp, session, promptTokens, replyTokens)
	}
	// 保存当前会话
	var chatItem model.ChatItem
	// 查询会话信息
	err = s.db.Where("chat_id = ?", session.ChatId).First(&chatItem).Error
	// err != nil 表示没有找到会话，用户第一次进行提问，就新建会话
	if err != nil {
		chatItem.ChatId = session.ChatId
		chatItem.UserId = userResp.Id
		chatItem.RoleId = role.Id
		chatItem.ModelId = session.Model.Id
		// 判断用户输入的提示词是否大于 30，如果大于就截取，我们使用用户的提示词作为会话的标题
		// utf8.RuneCountInString 该函数通过解析 UTF-8 编码的字符串，返回其中包含的 Unicode 字符（码位）的数量。例如，中文字符在 UTF-8 中通常占用 3 字节，但会被计为 1 个字符
		if utf8.RuneCountInString(usage.Prompt) > 30 {
			// rune 是 int32 的别名（type rune = int32），用于表示一个 Unicode 码点（Code Point），支持所有 Unicode 字符，包括多字节字符（如中文、Emoji 等）
			// 字符串本质是 UTF-8 编码的字节序列，而 []rune 是 Unicode 码点的切片
			// []rune(s) 会将字符串按 Unicode 字符逐个解码为码点，生成对应的 int32 切片
			chatItem.Title = string([]rune(usage.Prompt)[:30]) + "..."
		} else {
			chatItem.Title = usage.Prompt
		}
		chatItem.Model = req.Model
		// 保存会话
		err = s.db.Create(&chatItem).Error
		if err != nil {
			log.Errorf("保存用户会话失败：%v", err)
		}
	}
}

func getTotalTokens(req openai.ChatCompletionRequest) int {
	encode := utils.JsonEncode(req.Messages)
	// 消息切片map，每个消息是一个map
	var items []map[string]interface{}
	err := utils.JsonDecode(encode, &items)
	if err != nil {
		return 0
	}
	tokens := 0
	for _, item := range items {
		// 获取消息内容
		content, ok := item["content"]
		if ok && !utils.IsEmptyValue(content) {
			// 计算消息消耗的token数量
			t, err := utils.CalcTokens(utils.InterfaceToString(content), req.Model)
			if err == nil {
				tokens += t
			}
		}
	}
	return tokens
}

// subUserPower 扣减用户算力
func (s *OpenAiChatService) subUserPower(userResp response.User, session *types.ChatSession, promptTokens int, replyTokens int) {
	power := 1
	// 设置模型消耗的算力
	if session.Model.Power > 0 {
		power = session.Model.Power
	}
	err := s.userService.DecreasePower(int(userResp.Id), power, model.PowerLog{
		Type:   types.PowerConsume,
		Model:  session.Model.Value,
		Remark: fmt.Sprintf("模型名称：%s, 提问长度：%d，回复长度：%d", session.Model.Name, promptTokens, replyTokens),
	})
	if err != nil {
		log.Errorf("扣减用户算力时出现错误：%v", err)
	}
}

// executeTools 执行工具调用
func (s *OpenAiChatService) executeTools(toolCallsMap map[int]*types.ToolCall, ws *types.WsClient, contents *[]string,
	userResp response.User) error {
	// 循环遍历执行工具
	for _, v := range toolCallsMap {
		// 查询函数工具
		var function model.Function
		res := s.db.Where("name = ?", v.Function.Name).First(&function)
		if res.Error == nil {
			callMsg := fmt.Sprintf("\n正在调用工具 `%s` 作答 ...\n\n", function.Label)
			// 响应给前端消息
			utils.SendChunkMsg(ws, callMsg)
			*contents = append(*contents, callMsg)
			// 执行工具
			params := make(map[string]interface{})
			// 对参数进行解码
			_ = utils.JsonDecode(v.Function.Arguments, &params)
			log.Debugf("函数名称：%s，函数参数：%s", function.Name, params)
			params["user_id"] = userResp.Id
			var apiRes types.BizVo
			// 发送请求
			r, err := req2.C().R().SetHeader("Body-Type", "application/json").
				SetHeader("Authorization", function.Token).SetBody(params).SetSuccessResult(&apiRes).Post(function.Action)
			errMsg := ""
			if err != nil {
				errMsg = err.Error()
			} else {
				// 读取响应数据
				all, _ := io.ReadAll(r.Body)
				// 解码响应数据
				err = json.Unmarshal(all, &apiRes)
				if err != nil {
					errMsg = err.Error()
				} else if apiRes.Code != types.Success {
					errMsg = apiRes.Message
				}
			}
			if errMsg != "" {
				errMsg = "调用函数工具出错：" + errMsg
				*contents = append(*contents, errMsg)
			} else {
				errMsg = utils.InterfaceToString(apiRes.Data)
				*contents = append(*contents, errMsg)
			}
			utils.SendChunkMsg(ws, errMsg)
		} else {
			return fmt.Errorf("执行工具时操作数据库出现错误：%s", res.Error.Error())
		}
	}
	return nil
}

// chatCompletionRequest 大模型请求-同步请求
func (s *OpenAiChatService) chatCompletionRequest(ctx context.Context, req openai.ChatCompletionRequest,
	session *types.ChatSession) (openai.ChatCompletionResponse, error) {
	apiKey, err := s.checkApiKey(session)
	if err != nil {
		return openai.ChatCompletionResponse{}, err
	}
	// 创建 OpenAi 请求客户端
	client := NewOpenAiClient(apiKey.Value, apiKey.ApiURL)
	// 更新 API KEY 最后使用时间
	s.db.Model(&model.ApiKey{}).Where("id", apiKey.Id).UpdateColumn("last_used_at", time.Now().Unix())
	return client.CreateChatCompletion(ctx, req)
}

// chatCompletionRequestStream 大模型请求-流式请求
func (s *OpenAiChatService) chatCompletionRequestStream(ctx context.Context, req openai.ChatCompletionRequest,
	session *types.ChatSession) (*openai.ChatCompletionStream, error) {
	apiKey, err := s.checkApiKey(session)
	if err != nil {
		return nil, err
	}
	// 创建 OpenAi 请求客户端
	client := NewOpenAiClient(apiKey.Value, apiKey.ApiURL)
	// 更新 API KEY 最后使用时间
	s.db.Model(&model.ApiKey{}).Where("id", apiKey.Id).UpdateColumn("last_used_at", time.Now().Unix())
	return client.CreateChatCompletionStream(ctx, req)
}

// checkApiKey 检查API KEY，如果找到就返回
func (s *OpenAiChatService) checkApiKey(session *types.ChatSession) (*model.ApiKey, error) {
	apiKey := &model.ApiKey{}
	// 如果模型绑定了 API KEY，直接使用它
	if session.Model.KeyId > 0 {
		s.db.Where("id", session.Model.KeyId).Find(apiKey)
	}
	// 判断秘钥是否存在，如果不存在查找最后一个未使用的秘钥
	if apiKey.Id == 0 {
		s.db.Where("type", "chat").Where("enabled", true).Order("last_used_at asc").First(apiKey)
	}
	if apiKey.Id == 0 {
		return nil, errors.New("no available key 没有查询到可用的秘钥，请添加秘钥。")
	}
	return apiKey, nil
}

// handlerPromptImgUrl 处理提示词中图片URL
func (s *OpenAiChatService) handlerPromptImgUrl(prompt string) *openai.ChatCompletionMessage {
	text := prompt
	// 提取提示词中的 图片 地址
	imgURLs := utils.ExtractImgURLs(prompt)
	log.Debugf("提示词中的图片地址：%+v\n", imgURLs)
	if len(imgURLs) > 0 {
		data := make([]openai.ChatMessagePart, 0)
		data = append(data, openai.ChatMessagePart{
			Type: openai.ChatMessagePartTypeText,
			Text: strings.TrimSpace(text),
		})
		for _, v := range imgURLs {
			// 在 text 中查找子字符串 v，并将第一个匹配项替换为空字符串，1替换次数，表示仅替换第一个匹配项
			text = strings.Replace(text, v, "", 1)
			// 把图片转成 base64
			imageData, err := utils.URLImgToBase64(v)
			if err == nil {
				v = imageData
			} else {
				log.Errorf("图片转成Base64出现错误：%s\n", err.Error())
			}
			data = append(data, openai.ChatMessagePart{
				Type: openai.ChatMessagePartTypeImageURL,
				ImageURL: &openai.ChatMessageImageURL{
					URL: v, // 替换为 imageData 可改用Base64
					// Detail: "low", // 可选：图像细节控制（low/high）
				},
			})
		}
		msgPart := data[0]
		msgPart.Text = text
		data[0] = msgPart
		chatMsg := openai.ChatCompletionMessage{
			Role:         openai.ChatMessageRoleUser,
			MultiContent: data,
		}
		return &chatMsg
	}
	return nil
}

// handlerPromptFileUrl 处理提示词中特定的文件URL
func (s *OpenAiChatService) handlerPromptFileUrl(prompt string, session *types.ChatSession) (string, error) {
	fullPrompt := prompt
	text := prompt
	// 从提示词中提取特定文件的URL
	files := utils.ExtractFileURLs(prompt)
	log.Debugf("从提示词中提取特定文件的URL：%v\n", files)
	if len(files) > 0 {
		contents := make([]string, 0)
		var file model.File
		for _, v := range files {
			// 根据URL查询文件信息
			s.db.Where("url = ?", v).First(&file)
			// 读取文件内容
			content, err := utils.ReadFileContent(v, s.app.Config.TikaHost)
			if err != nil {
				log.Errorf("读物文件出现错误：%v\n", err)
			} else {
				contents = append(contents, fmt.Sprintf("%s 文件内容：%s", file.Name, content))
			}
			// 把文件 URL 从提示词文本中去除掉
			text = strings.Replace(text, v, "", 1)
		}
		// 重新生成新的提示词
		if len(contents) > 0 {
			fullPrompt = fmt.Sprintf("请根据提供的文件内容信息回答问题(其中Excel 已转成 HTML)：\n\n %s\n\n 问题：%s",
				strings.Join(contents, "\n"), text)
		}
		// 重新计算消耗的token
		tokens, _ := utils.CalcTokens(fullPrompt, session.Model.Value)
		if tokens > session.Model.MaxContext {
			return "", fmt.Errorf("文件的长度超出模型允许的最大上下文长度，请减少文件内容数量或文件大小。")
		}
	}
	return fullPrompt, nil
}

// loadChatCtx 加载聊天上下文
func (s *OpenAiChatService) loadChatCtx(session *types.ChatSession, role model.ChatRole,
	openAiReq openai.ChatCompletionRequest, promptTokens int) []openai.ChatCompletionMessage {
	chatCtxs := make([]openai.ChatCompletionMessage, 0)
	messages := make([]interface{}, 0)
	if s.app.SysConfig.EnableContext {
		// 获取历史消息（缓存或数据库）
		if s.ChatContexts.Has(session.ChatId) {
			// 直接复用缓存数据
			messages = s.ChatContexts.Get(session.ChatId)
		} else {
			presetsMessage := make([]openai.ChatCompletionMessage, 0)
			_ = utils.JsonDecode(role.Context, &presetsMessage)
			for _, presetsMsg := range presetsMessage {
				messages = append(messages, presetsMsg)
			}
			var historyMessage []model.ChatMessage
			// 从数据库查询历史消息（按时间正序：旧→新）
			if s.app.SysConfig.ContextDeep > 0 {
				s.db.Where("chat_id = ? and use_context = 1", session.ChatId).
					Limit(s.app.SysConfig.ContextDeep).Order("id desc").Find(&historyMessage)
				// 合并历史消息到聊天上下文
				for i := len(historyMessage) - 1; i >= 0; i-- {
					msg := historyMessage[i]
					ms := openai.ChatCompletionMessage{Role: openai.ChatMessageRoleUser, Content: msg.Content}
					// 判断消息类似如果是大模型回复的消息就设置角色为 assistant
					if msg.Type == types.ReplyMsg {
						ms.Role = openai.ChatMessageRoleAssistant
					}
					messages = append(messages, ms)
				}
			}
		}

		// 最大响应Token
		tokens := openAiReq.MaxTokens
		// 计算调用工具消耗的token
		tks, _ := utils.CalcTokens(utils.JsonEncode(openAiReq.Tools), openAiReq.Model)
		tokens += tks + promptTokens
		messages = getLastElements(messages, s.app.SysConfig.ContextDeep)
		for i := 0; i < len(messages); i++ {
			v := messages[i]
			tks, _ = utils.CalcTokens(utils.JsonEncode(v), openAiReq.Model)
			// 上下文 token 超出了模型的最大上下文长度
			if tokens+tks >= session.Model.MaxContext {
				break
			}
			// 上下文的深度超出了模型的最大上下文深度
			if len(chatCtxs) >= s.app.SysConfig.ContextDeep {
				break
			}
			tokens += tks
			ccm := openai.ChatCompletionMessage{}
			if err := utils.CopyObject(v, &ccm); err != nil {
				log.Errorf("拷贝对象数据出现错误：%v\n", err)
				continue
			}
			chatCtxs = append(chatCtxs, ccm)
		}
	}
	return chatCtxs
}

// getLastElements 获取一个切片最后num个元素
func getLastElements(slice []interface{}, num int) []interface{} {
	if len(slice) < num {
		// 如果切片长度小于num，返回整个切片
		return slice
	}
	// 获取最后的num个元素
	return slice[len(slice)-num:]
}

// loadFunc 加载函数工具
func (s *OpenAiChatService) loadTool(session *types.ChatSession) []openai.Tool {
	if len(session.Tools) > 0 {
		var items []model.Function
		// 查询函数工具
		res := s.db.Where("enabled", true).Where("id IN ?", session.Tools).Find(&items)
		if res.Error == nil {
			var tools = make([]openai.Tool, 0)
			for _, v := range items {
				var params map[string]interface{}
				err := utils.JsonDecode(v.Parameters, &params)
				if err != nil {
					continue
				}
				tool := openai.Tool{
					Type: openai.ToolTypeFunction,
					Function: &openai.FunctionDefinition{
						Name:        v.Name,
						Description: v.Description,
						Parameters:  params,
					},
				}
				// parameters["required"] 判断是否有 required 参数，如果没有就设置默认的 required 参数
				if v, ok := params["required"]; v == nil || !ok {
					params["required"] = []string{}
				}
				tools = append(tools, tool)
			}
			return tools
		}
	}
	return nil
}

// checkPromptToken 计算提示词消耗的Token
func (s *OpenAiChatService) checkPromptToken(prompt string, session *types.ChatSession) (int, error) {
	// 计算提示词消耗的token
	promptTokens, err := utils.CalcTokens(prompt, session.Model.Value)
	if err != nil {
		return 0, fmt.Errorf("计算提示词的token失败：%v", err)
	}

	// 检查prompt长度是否超过了当前模型允许的最多上下文长度
	if promptTokens > session.Model.MaxContext {
		return 0, errors.New("对话内容超出了当前模型允许的最大上下文长度！")
	}

	return promptTokens, nil
}

// checkUserInfo 检查用户信息
func (s *OpenAiChatService) checkUserInfo(session *types.ChatSession) (*response.User, error) {
	var user model.User
	// 查询用户信息
	res := s.db.Model(&model.User{}).First(&user, session.UserId)
	if res.Error != nil {
		return nil, errors.New("未授权用户，你正在进行非法操作！")
	}
	var userResp response.User
	err := utils.CopyObject(user, &userResp)
	userResp.Id = user.Id
	if err != nil {
		return nil, errors.New("User 对象转换失败：" + err.Error())
	}

	if userResp.Status == false {
		return nil, errors.New("您的账号已经被禁用，如有疑问，请联系管理员！")
	}

	//  判断用户的算力是否小于模型使用一次所消耗的算力
	if userResp.Power < session.Model.Power {
		return nil, fmt.Errorf("您当前剩余算力 %d 已不足以支付当前模型的单次对话所需要消耗的算力 %d，[立即购买](/member)。", userResp.Power, session.Model.Power)
	}

	return &userResp, nil
}
